/*
 * Created on 2008-7-30
 *
 * TODO
 */
package jacky.lanlan.song.junitx.v4.conc;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.*;

import org.junit.internal.runners.InitializationError;
import org.junit.internal.runners.JUnit4ClassRunner;
import org.junit.internal.runners.MethodRoadie;
import org.junit.internal.runners.TestMethod;
import org.junit.runner.Description;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunNotifier;


/**
 * 为JUnit4的测试提供并发测试功能的Runner，兼容JUnit3测试类。
 * <p>
 * 这个Runner会自动探测那些标注了 <code>Concurrent</code> 的测试方法(<i>这意味着要并发执行一个测试，必须同时标注
 * <code>Concurrent</code> 和 <code>org.junit.Test</code> </i>)，然后通过一个线程池来并发执行。<br/>
 * 每个方法都可以设置不同的并发执行次数以及并发量(执行线程数量)。
 * <p>
 * 如果需要使用自定义的线程池，可以通过在测试类上标注 <code>ConcurrentTest</code>来配置。
 * <p style="color:red;font-weight:bolder">
 * 执行并发测试并不能保证可以暴露并发问题，可能需要多次尝试，但通过增加并发执行次数和并发量会对此有帮助（通过
 * <code>Concurrent</code> 标注来设置）。
 * @see ConcurrentRun
 * @see Concurrent
 * @author Jacky.Song
 */
public class ConcurrentRunner extends JUnit4ClassRunner {
	private ThreadPoolExecutor pool = ThreadPoolFactory.defaultFactory.createFactory();
	
	public ConcurrentRunner(Class<?> klass) throws InitializationError {
		super(klass);
		
		ConcurrentRun ct = klass.getAnnotation(ConcurrentRun.class);
		try {
			if(ct!=null) {// 如果提供了非默认的Factory
				pool = ct.factory().newInstance().createFactory();
			}
		}
		catch (Exception e) {
			throw new RuntimeException(e);
		}
		
		cfgThreadPool(ThreadPoolFactory.DEFAULT_SIZE);
		pool.prestartCoreThread();// 预先启动默认的线程
	}

	private void cfgThreadPool(int corePoolSize) {
		int size = corePoolSize<=ThreadPoolFactory.DEFAULT_SIZE ?
				ThreadPoolFactory.DEFAULT_SIZE : corePoolSize;// 至少要保留默认的线程
		this.pool.setCorePoolSize(size);
		// TODO 下面的参数可能需要微调
		this.pool.setMaximumPoolSize(size+1);
		this.pool.setKeepAliveTime(500, TimeUnit.MILLISECONDS);// 这个时间是两个并发测试之间的间隔时间
	}
	
	@Override
	protected void invokeTestMethod(final Method method, final RunNotifier notifier) {
		// COPYED FROM JUnit4ClassRunner
		final Description description= methodDescription(method);
		final Object test;
		try {
			test= createTest();
		} catch (InvocationTargetException e) {
			notifier.fireTestFailure(new Failure(description, e.getCause()));
			return;
		} catch (Exception e) {
			notifier.fireTestFailure(new Failure(description, e.getCause()));
			return;
		}
		final TestMethod testMethod= wrapMethod(method);
		// FINISHED
		
		MethodRoadie mr=null;
		if(method.isAnnotationPresent(Concurrent.class)) {// 并发测试，使用ConcurrentMethodRoadie
			ConcurrentMethodRoadie cmr=new ConcurrentMethodRoadie(test, testMethod, notifier, description);
			cmr.setOrigiTestMethod(method);
			cmr.setThreadPool(pool);
			ResultListenerReplacer.replace(notifier);// 处理ResultListener
			mr=cmr;
		}
		else {// 一般的方法，和JUnit4ClassRunner的处理方式一样，使用MethodRoadie
			mr=new MethodRoadie(test, testMethod, notifier, description);
		}
		mr.run();
	}
	
}
